深入理解 ADB 协议

您所在的位置:网站首页 sonic tools 安卓 深入理解 ADB 协议

深入理解 ADB 协议

2023-03-17 19:32| 来源: 网络整理| 查看: 265

缝缝补补,还是把这篇文章写完了。

前言

整体感受一下这篇文章研究的东西最后带来了啥。

安卓免 ROOT 实现 ADB 连接另一台安卓,这里的手环是 ow2。

camera1.jpg

安卓免 ROOT 给另一台安卓安装 app

camera2.jpg

ADB简单介绍

ADB 是安卓调试桥(Android Debug Bridge),为了实现分布式(这个分布式的确是官方的词儿),分离出了 ADB server,ADB server 与安卓设备上的 adbd 进程通信。

分离的这层 ADB server 有什么用呢?

例如 PC A 连接了10台安卓,此时 ADB server 运行在 PC A 上,同局域网的其他 PC 只需要通过 PC A 的5037端口即可调试10台安卓,这就是 server 带来的好处。 这个实际参数就是adb -L

-L SOCKET listen on given socket for adb server [default=tcp:localhost:5037] 复制代码

并且这个 server 不随 adb 命令的本身退出而退出,就能减少与各个设备的握手过程。

ADB 同时也有一系列自己的协议,负责与设备通信,在 adb 二进制中,第一次使用 adb devices 或者主动执行 adb start-server 的时候,adb 会 fork 出这个 server,绑定端口在5037,随后 adb 作为一个 client 与5037端口的 server 通信,例如执行 adb devices 后,adb 并不直接与设备进行串口通信,而是告诉 server 自己执行 adb devices 命令,server 与设备串口或者网络通信,随后将结果交给 adb client。

adb client 与 adb server 还有一些通信协议我们不关心,我们关心 adb server 与 adbd(安卓设备上的响应 adb 消息的进程) 之间的通信。

我们需要实现不借助任何编译的 adb 二进制也能实现 adb 的功能

此处看起来需要一个架构图,但是还不会画🤫

为什么安卓需要使用ADB?

这里的意思不是 PC 连接安卓,而是安卓连接安卓,adb 有着 shell 的用户组,这个用户组比普通的 app 权限高很多,可以直接截屏/录屏,冻结/杀死 app,卸载普通/系统的 app。

所有就有了一系列软件如冰箱、小黑屋、Shizuku,他们本身有一个 shell 的 server,但是需要连上 pc 后,用户主动粘贴一行脚本执行,启动自身的服务进程,由于是 adb 启动,所以这个服务进程也有 shell 的能力,如此一来,只要手机不重启,这些 app 就能通过启动的 server 获得 shell 权限。

甚至像 shizuku,可以直接通过 AIDL 通信将自己的 server 给任意 app 赋予 shell 权限(其它app需要引入shizuku api),一些没能力 root 设备的用户,或者一些不能 root 的手机(华为),他们同样可以获得 shell 权限,来更好的管理他们的安卓设备,控制进程。 但是,并不是所有的用户都有电脑!并且想使用这个功能的时候并不在电脑旁边,所以,需求产生了

目前安卓实现 ADB 连接另一台安卓的方案

在 Magisk 的 github.com/Magisk-Modu… ,就是将 adb 交叉编译到安卓的,这类跨平台的移植,肯定是少不了 patch 的,这个过程也不难,在 android-tools 也有现成的方案。

这个库是自己 fork termux package 后维护的。

并且也是我一直在用的方案,此时 adb 可以连接网络设备,但是当另一台安卓设备通过 otg 连接的时候,adb devices 总是查不到这个设备,原因很简单,安卓与 Linux 的内核有差异,在连接串口设备的时候,Linux 会尝试识别并驱动这个设备,最后会在 /dev 下产生一个字符设备,并且普通用户很容易就能读写到这个设备。

举个例子:

crw-rw-rw- 1 root wheel 9, 0 11 12 10:05 tty.Bluetooth-Incoming-Port 复制代码

可以看到开头的字符是 c,而不是 d 或者 -,它代表的是字符设备。

而当一个串口设备连接到安卓的时候,产生的字符设备文件,普通用户是不可读的,原因也非常简单,摄像头,麦克风这类设备都是作为串口连接到安卓,如果这些字符设备普通权限就能读写的话,那安卓的权限机制的存在就没有意义了。

那如果通过 root 权限呢?答案是肯定的,在root 权限的加持下,安卓上的 adb 基本就跟 PC 一样了,但是,用户手机可能并没有root!!!需求又产生了

至于内核默认能驱动哪些设备,在编译内核的时候,是可选的,除了常规的键鼠之外,我们还能让内核可以驱动 ch340x/cp210x 这类的驱动,但是这个成本较高,所以 PC 可以支持安装驱动,

用户:我想要使用 SHELL 权限,我甚至比你一个开发者还明白这是为什么,但是我没有电脑,我设备也没有root。

开发者:呜呜呜~

突破点分析

如上面的分析,问题的解法也出来了,我们不能通过串口通信去使用摄像头,麦克风,但我们能通过安卓的 api 申请权限使用到那些设备,安卓普通 app 虽然无法直接访问 /dev/ 下的字符设备,那我们同样也能通过安卓的 api,读写 usb 设备。

这部分设计很多 Android USB 知识,本篇不做详细讨论,仓库中有所有代码

我也是狂补了几波 USB 相关的文章,然后去 github 上翻到一个勉强能用的开源修修补补才实现的。

读写 OTG 获取权限

能够识别到安卓设备连接的 device_filter.xml

复制代码

这部分写到 manifest

复制代码

申请权限

UsbDevice device = getIntent().getParcelableExtra(UsbManager.EXTRA_DEVICE); if (device != null) { // 说明是静态广播启动起来的 // 用户再收到系统弹窗"是否打开ADB工具",用户点击了是 System.out.println("From Intent!"); asyncRefreshAdbConnection(device); } else { System.out.println("From onCreate!"); for (String k : mManager.getDeviceList().keySet()) { UsbDevice usbDevice = mManager.getDeviceList().get(k); if (mManager.hasPermission(usbDevice)) { asyncRefreshAdbConnection(usbDevice); } else { mManager.requestPermission(usbDevice, PendingIntent.getBroadcast(getApplicationContext(), 0, new Intent(Message.USB_PERMISSION), 0)); } } } 复制代码

这部分就英雄所见略同了~

初始化 OTG UsbEndpoint epOut = null; UsbEndpoint epIn = null; // look for our bulk endpoints for (int i = 0; i < intf.getEndpointCount(); i++) { UsbEndpoint ep = intf.getEndpoint(i); if (ep.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) { if (ep.getDirection() == UsbConstants.USB_DIR_OUT) { epOut = ep; } else { epIn = ep; } } } if (epOut == null || epIn == null) { throw new IllegalArgumentException("not all endpoints found"); } mEndpointOut = epOut; mEndpointIn = epIn; 复制代码

这段代码到处都能抄到。

读 public void readx(byte[] buffer, int length) throws IOException { UsbRequest usbRequest = getInRequest(); ByteBuffer expected = ByteBuffer.allocate(length).order(ByteOrder.LITTLE_ENDIAN); usbRequest.setClientData(expected); if (!usbRequest.queue(expected, length)) { throw new IOException("fail to queue read UsbRequest"); } while (true) { UsbRequest wait = mDeviceConnection.requestWait(); if (wait == null) { throw new IOException("Connection.requestWait return null"); } ByteBuffer clientData = (ByteBuffer) wait.getClientData(); wait.setClientData(null); if (wait.getEndpoint() == mEndpointOut) { // a write UsbRequest complete, just ignore } else if (expected == clientData) { releaseInRequest(wait); break; } else { throw new IOException("unexpected behavior"); } } expected.flip(); expected.get(buffer); } 复制代码 写 public void writex(byte[] buffer) throws IOException { Log.i("Nightmare", ">>>>>>>>" + new String(buffer)); int offset = 0; int transferred = 0; while ((transferred = mDeviceConnection.bulkTransfer(mEndpointOut, buffer, offset, buffer.length - offset, defaultTimeout)) >= 0) { offset += transferred; if (offset >= buffer.length) { break; } } if (transferred < 0) { throw new IOException("bulk transfer fail"); } } 复制代码 ADB协议分析

ADB 协议算应用层协议,这种串口通信协议,都会有一些握手的机制,类似于 tcp 的握手,简单说,就是对一些口令,因为会通过 usb 向手机写入东西的不仅是 adb,可能是 MTP 拷贝文件,它不能误识别。

实现 adb connect

在PC第一次连接安卓时,安卓上会有个弹窗,问是否允许调试,其实就是 PC 请求连接安卓设备,点击确认后才算连接结束,之后再次连接就没有这个弹窗了,但是这个请求过程还是在的 所以第一步就是安卓如何实现 otg 成功连接另一台安卓,并唤起调试弹窗。

我将整个协议用聊天的方式表述,右边表示写入内容,代表 PC,左边代表返回内容,代表安卓。 $符号连接的字符串代表变量,AUTH 与 CNXN 等代表字符本身,+符号代表连接,方便表示,不代表协议中有这个字符。 ()间的内容为解释说明

首次连接 CNXN + '$protcol_version' + '$maxdata' '$payload'($payload是host::\0) AUTH '$token1' AUTH '$signature'(将安卓端发送回来的$token1签名后得到的) AUTH '$token2' AUTH '$RSA_PUBLIC_KEY' CNXN device::ro.product.name=elish;ro.product.model=M2105K81AC;ro.product.device=elish; features=sendrecv_v2_brotli,remount_shell,sendrecv_v2,abb_exec,fixed_push_mkdir, fixed_push_symlink_timestamp,abb,shell_v2,cmd,ls_v2,apex,stat_v2 复制代码

最后设备点击弹窗的允许后才会返回 device::.*,代表整个握手过程结束。

非首次连接 CNXN + '$protcol_version' + '$maxdata' '$payload' AUTH '$token1' AUTH '$signature'(将安卓端发送回来的$token1签名后得到的) CNXN device::ro.product.name=elish;ro.product.model=M2105K81AC;ro.product.device=elish;features=sendrecv_v2_brotli,remount_shell,sendrecv_v2,abb_exec,fixed_push_mkdir,fixed_push_symlink_timestamp,abb,shell_v2,cmd,ls_v2,apex,stat_v2 复制代码

少了一个要求发送 RSA 的过程。

以上全部经过代码验证。

as.png

连接成功后,后面想实现什么功能,在 SERVICES.TXT 和 SYNC.TXT 都能很快的找到了。

实现 adb shell 协议文档 shell:command arg1 arg2 ... Run 'command arg1 arg2 ...' in a shell on the device, and return its output and error streams. Note that arguments must be separated by spaces. If an argument contains a space, it must be quoted with double-quotes. Arguments cannot contain double quotes or things will go very wrong. Note that this is the non-interactive version of "adb shell" 复制代码 完整协议过程。 OPEN shell:cmd OKAY CLSE 复制代码 实际协议内容

箭头表示消息方向,不是具体协议内容,>>>>>>>>代表发送过去的消息,OPEN��������������-������+�������� >>>>>>>>shell:settings put system pointer_location 1�� shell:�� >>>>>>>WRTE������'������������w���������� >>>>>>>>w >>>>>>>d start WRTE DATA+'$len'($len是即将发送的字节长度) OKAY WRTE '$byte'(此次的字节,不能超过16k) OKAY < end start 到 end 循环直到一个文件被完整的发送 WRTE DONE(通知安卓 adbd 这个文件发送完了) OKAY WRTE OKAY WRTE QUIT(退出sync模式) OKAY CLSE 复制代码 实际协议内容 >>>>>>>>OPEN����������������������������� >>>>>>>>sync:�� WRTE������������������F�������� >>>>>>>>SEND������ WRTE��������������������������� >>>>>>>>/sdcard/switch_work.sh WRTE������������������*�������� >>>>>>>>,33206 WRTE������������������y�������� >>>>>>>>DATA_������ WRTE������������_������3"�������� >>>>>>>>this is file content byte WRTE��������������������������� >>>>>>>>DONE��� QUIT�������� tcpip:5555��



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3